package visitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import semantic.tables.ClassTable;
import semantic.tables.MethodTable;
import semantic.tables.ProgramTable;
import semantic.tables.Symbol;
import syntaxtree.AbstractNode;
import syntaxtree.And;
import syntaxtree.ArrayAssign;
import syntaxtree.ArrayLength;
import syntaxtree.ArrayLookup;
import syntaxtree.Assign;
import syntaxtree.Block;
import syntaxtree.BooleanType;
import syntaxtree.Call;
import syntaxtree.ClassDecl;
import syntaxtree.ClassDeclExtends;
import syntaxtree.ClassDeclSimple;
import syntaxtree.False;
import syntaxtree.Formal;
import syntaxtree.Identifier;
import syntaxtree.IdentifierExp;
import syntaxtree.IdentifierType;
import syntaxtree.If;
import syntaxtree.IntArrayType;
import syntaxtree.IntegerLiteral;
import syntaxtree.IntegerType;
import syntaxtree.LessThan;
import syntaxtree.MainClass;
import syntaxtree.MethodDecl;
import syntaxtree.Minus;
import syntaxtree.NewArray;
import syntaxtree.NewObject;
import syntaxtree.Not;
import syntaxtree.Plus;
import syntaxtree.Print;
import syntaxtree.Program;
import syntaxtree.Statement;
import syntaxtree.This;
import syntaxtree.Times;
import syntaxtree.True;
import syntaxtree.VarDecl;
import syntaxtree.While;
import temp.Label;
import temp.LabelList;
import temp.Temp;
import translate.Frag;
import translate.ProcFrag;
import tree.BINOP;
import tree.CALL;
import tree.CJUMP;
import tree.CONST;
import tree.ESEQ;
import tree.EXPSTM;
import tree.Exp;
import tree.ExpList;
import tree.JUMP;
import tree.LABEL;
import tree.MEM;
import tree.MOVE;
import tree.NAME;
import tree.SEQ;
import tree.Stm;
import tree.TEMP;
import frame.Access;
import frame.Frame;

public class TranslateToIRVisitor implements TranslateVisitor {

	private final ProgramTable programTable;
	private ClassTable currClass;
	private MethodTable currMethod;
	private Frame currFrame;

	public ProcFrag procFrag;
	private Frag currProcFrag;
	private final HashMap<Symbol, Exp> vars = new HashMap<Symbol, Exp>();
	private Exp currObj = null;

	public TranslateToIRVisitor(ProgramTable programTable, Frame initialFrame) {
		this.programTable = programTable;
		currFrame = initialFrame;
	}

	private void addFrag(Stm stm, Frame frame) {
		if (currProcFrag == null) {
			procFrag = new ProcFrag(stm, frame);
			currProcFrag = procFrag;
		} else {
			currProcFrag.next = new ProcFrag(stm, frame);
			currProcFrag = currProcFrag.next;
		}
	}

	public Object visit(AbstractNode n) {
		return VisitorUtil.visit(this, n);
	}

	public void visit(Program n) {
		visit(n.mainClass);
		for (ClassDecl classDecl : n.classList) {
			visit(classDecl);
		}
	}

	public Exp visit(MainClass n) {
		currClass = programTable.getClass(n.idClass.str);
		currFrame = currFrame.newFrame(Symbol.symbol("main"), new ArrayList<Boolean>());
		addFrag(toStm(visit(n.stm)), currFrame);
		currClass = null;
		return null;
	}

	public Exp visit(ClassDeclSimple n) {
		currClass = programTable.getClass(n.id.str);
		// Bypass nos atributos...
		for (MethodDecl method : n.methodList) {
			visit(method);
		}
		currClass = null;
		return null;
	}

	public Exp visit(ClassDeclExtends n) {
		currClass = programTable.getClass(n.id.str);
		// Bypass nos atributos...
		for (MethodDecl method : n.methodList) {
			visit(method);
		}
		currClass = null;
		return null;
	}

	public Exp visit(VarDecl n) {
		/*
		 * VERIFICAR SE VARDECL EH GENERICO MESMO PRA OBJETOS E ARRAYS. FAZ SENTIDO SER SE O CONTEUDO DESTA ALOCAÇÃO TAMBÉM FOR
		 * PORNTEIRO PARA OBJETO E PONTEIRO PARA ARRAY.
		 */
		/*
		 * The exp method of Access is used to turn an Access into the Tree expression. The Exp argument is the address of the
		 * stack that the Access lives in.
		 */
		Exp exp = currFrame.allocLocal(false).exp(new TEMP(currFrame.FP()));
		vars.put(Symbol.symbol(currMethod.id + "$" + n.id.str), exp);
		return exp;
	}

	public Stm visit(MethodDecl n) {
		LABEL methodLabel = new LABEL(new Label(currClass.id.toString() + "$" + n.id.str));
		currMethod = currClass.getMethod(n.id.str);

		List<Boolean> formals = new ArrayList<Boolean>();
		for (int i = 0; i < n.formalList.size(); i++) {
			formals.add(false);
			visit(n.formalList.elementAt(i));
		}
		formals.add(false); // Parâmetro que carrega para passagem do objeto corrente (this)

		currFrame = currFrame.newFrame(Symbol.symbol(methodLabel.label.toString()), formals);
		currObj = currFrame.formals.get(currFrame.formals.size() - 1).exp(new TEMP(currFrame.FP()));

		for (int i = 0; i < n.varList.size(); i++) {
			visit(n.varList.elementAt(i));
		}

		Stm stm = null;
		// Cria Stm para Statement
		if (n.stmList.size() > 0) {
			stm = toStm(visit(n.stmList.elementAt(0)));
			for (int i = 1; i < n.stmList.size(); i++) {
				stm = new SEQ(stm, toStm(visit(n.stmList.elementAt(i))));
			}
		}

		// Cria Stm para ReturnExp
		Exp returnExp = (Exp) visit(n.returnExp);
		if (returnExp != null) {
			Stm returnStm = new MOVE(new TEMP(currFrame.RV()), returnExp);
			if (stm == null) {
				stm = returnStm;
			} else {
				stm = new SEQ(stm, returnStm);
			}
		}

		if (stm == null) {
			stm = new EXPSTM(new CONST(0));
		}
		addFrag(stm, currFrame);
		currMethod = null;
		currObj = null;
		return stm;
	}

	public Exp visit(Formal n) {
		// Mesmo do VarDecl
		return visit(new VarDecl(n.type, n.id));
	}

	public Exp visit(IntArrayType n) {
		return new CONST(0);
	}

	public Exp visit(BooleanType n) {
		return new CONST(0);
	}

	public Exp visit(IntegerType n) {
		return new CONST(0);
	}

	public Exp visit(IdentifierType n) {
		return new CONST(0);
	}

	public Stm visit(Block n) {
		Stm stm = null;
		for (Statement statement : n.stmList) {
			if (stm == null) {
				stm = toStm(visit(statement));
			} else {
				stm = new SEQ(stm, toStm(visit(statement)));
			}
		}
		if (stm == null) {
			stm = new EXPSTM(new CONST(0));
		}
		return stm;
	}

	public Stm visit(If n) {
		Label t = new Label();
		Label f = new Label();
		Label join = new Label();
		return new SEQ(
				new CJUMP(CJUMP.EQ, (Exp) visit(n.e), new CONST(1), t, f),
					new SEQ(new LABEL(f),
							new SEQ(toStm(visit(n.s2)),
									new SEQ(new JUMP(new NAME(join), new LabelList(join, null)),
											new SEQ(new LABEL(t),
													new SEQ(toStm(visit(n.s1)), new LABEL(join)))))));
	}

	public Stm visit(While n) {
		Label test = new Label();
		Label t = new Label();
		Label f = new Label();
		return new SEQ(new SEQ
					(new SEQ(new LABEL(test),
							(new CJUMP(CJUMP.EQ, (Exp) visit(n.e),
									new CONST(1), t, f))),
							(new SEQ(new LABEL(t), toStm(visit(n.s))))),
					new LABEL(f));
	}

	public Exp visit(Print n) {
		return currFrame.externalCall("print", new ExpList((Exp) visit(n.e), null));
	}

	public Stm visit(Assign n) {
		return new MOVE(getVar(Symbol.symbol(currMethod.id + "$" + n.i.str)), (Exp) visit(n.e));
	}

	public Stm visit(ArrayAssign n) {
		return new MOVE(new MEM(
				new BINOP(BINOP.PLUS,
						getVar(Symbol.symbol(currMethod.id + "$" + n.i.str)),
						(Exp) visit(n.e1))
				), (Exp) visit(n.e2));
	}

	public Exp visit(And n) {
		return new BINOP(BINOP.AND, (Exp) visit(n.e1), (Exp) visit(n.e2));
	}

	public Exp visit(LessThan n) {
		Exp e1 = (Exp) visit(n.e1);
		Exp e2 = (Exp) visit(n.e2);
		TEMP r = new TEMP(new Temp());
		Label verdade = new Label();
		Label falso = new Label();
		Label saiFora = new Label();
		return new ESEQ(new SEQ(
					new CJUMP(CJUMP.LT, e1, e2, verdade, falso),
						new SEQ(new LABEL(falso), new SEQ(new MOVE(r, new CONST(0)),
								new SEQ(new JUMP(saiFora), new SEQ(new LABEL(verdade),
										new SEQ(new MOVE(r, new CONST(1)), new LABEL(saiFora))))))), r);
	}

	public Exp visit(Plus n) {
		Exp e1 = (Exp) visit(n.e1);
		Exp e2 = (Exp) visit(n.e2);
		return new BINOP(BINOP.PLUS, e1, e2);
	}

	public Exp visit(Minus n) {
		Exp e1 = (Exp) visit(n.e1);
		Exp e2 = (Exp) visit(n.e2);
		return new BINOP(BINOP.MINUS, e1, e2);
	}

	public Exp visit(Times n) {
		Exp e1 = (Exp) visit(n.e1);
		Exp e2 = (Exp) visit(n.e2);
		return new BINOP(BINOP.MUL, e1, e2);
	}

	public Exp visit(ArrayLookup n) {
		return new BINOP(BINOP.PLUS, (Exp) visit(n.e1), (Exp) visit(n.e2));
	}

	public Exp visit(ArrayLength n) {
		// A primeira posição do array é seu tamanho
		return new BINOP(BINOP.PLUS, (Exp) visit(n.e1), new CONST(0));
	}

	public Exp visit(Call n) {
		// Exp do objeto cuja função está sendo chamada:
		Exp caller = (Exp) visit(n.e1);
		// Passa como parâmetro para chamada de função o objeto:
		ExpList expList = new ExpList(caller, null);
		// Passa os demais parâmetros:
		for (int i = n.el.size() - 1; i >= 0; i--) {
			expList = new ExpList((Exp) visit(n.el.elementAt(i)), expList);
		}
		return new CALL(caller, expList);
	}

	public Exp visit(IntegerLiteral n) {
		return new CONST(n.i);
	}

	public Exp visit(True n) {
		return new CONST(1);
	}

	public Exp visit(False n) {
		return new CONST(0);
	}

	public Exp visit(IdentifierExp n) {
		// DEVE PROCURAR 1o NO FRAME E 2o NO OBJETO CORRENTE O TEMP DESTE IDENTIFICADOR
		// TODO REVISAR ESTE
		Symbol symbol = Symbol.symbol(currMethod.id + "$" + n.s1);
		Exp exp = getVar(symbol);
		if (exp == null) {
			exp = getVar(Symbol.symbol("" + "$" + n.s1));
		}
		return exp;
	}

	public Exp visit(This n) {
		List<Access> accList = currFrame.formals;
		Access objAccess = accList.get(accList.size() - 1);
		// Retorna o TEMP que contém o objeto:
		return objAccess.exp(new TEMP(currFrame.FP()));
	}

	public Exp visit(NewArray n) {
		return currFrame.externalCall("initArray",
				new ExpList(new BINOP(BINOP.MUL,
						new BINOP(BINOP.PLUS, (Exp) visit(n.e1), new CONST(1)),
						new CONST(currFrame.wordsize())), null));
	}

	public Exp visit(NewObject n) {
		ClassTable objClass = programTable.getClass(n.i1.str);
		return currFrame.externalCall("allocRecord",
				new ExpList(new BINOP(BINOP.MUL,
						new CONST(objClass.getVars().size() + 1),
						new CONST(currFrame.wordsize())
						), null));
	}

	public Exp visit(Not n) {
		return new BINOP(BINOP.MINUS, new CONST(1), (Exp) visit(n.e1));
	}

	public Exp visit(Identifier n) {
		return getVar(Symbol.symbol(currMethod.id + "$" + n.str));
	}

	private Stm toStm(Object expStm) {
		if (expStm instanceof Exp) {
			return new EXPSTM((Exp) expStm);
		}
		return (Stm) expStm;
	}

	private Exp getVar(Symbol symbol) {
		Exp var = vars.get(symbol);
		if (var == null) {
			// SE NAO EH VAR LOCAL, TEM QUE PEGAR DA INSTANCIA CORRENTE.
			// FIXME FALTA PEGAR O DESLOCAMENTO DA VARIAVEL
			return currObj;
		} else {
			return var;
		}
	}
}
